<template>
  <transition name="viewer-fade">
    <div ref="wrapper" :tabindex="-1" class="media-viewer__wrapper" :style="{ zIndex }">
      <div class="media-viewer__mask" @click.self="hideOnClickModal && hide()"></div>
      <!-- CLOSE -->
      <span class="media-viewer__btn media-viewer__close" @click="hide">
        <el-icon><Close /></el-icon>
      </span>

      <!-- CANVAS -->
      <div class="media-viewer_content">
        <div class="media-viewer__canvas">
          <img
            v-for="(url, i) in urlList"
            v-show="i === index && isImage"
            ref="media"
            :key="url"
            :src="url"
            :style="mediaStyle"
            class="media-viewer__img"
            @load="handleMediaLoad"
            @error="handleMediaError"
            @mousedown="handleMouseDown"
          />
          <video
            controls
            v-for="(url, i) in urlList"
            v-show="i === index && isVideo"
            ref="media"
            :key="url"
            :src="url"
            :style="mediaStyle"
            class="media-viewer__img"
            @load="handleMediaLoad"
            @error="handleMediaError"
            @mousedown="handleMouseDown"
          ></video>
        </div>
        <div class="media-viewer__operation">
          <!-- ARROW -->
          <slot name="extra-info" v-bind="{ index }"></slot>
          <!-- <template > -->
          <div class="flex justify-start items-center w-full gap-2 mt-6 mb-2">
            <div class="media-viewer__arrow" v-if="!isSingle">
              <span
                class="media-viewer__btn media-viewer__prev"
                vv-show="infinite || !isFirst"
                :class="{ 'first-arrow': !infinite && isFirst }"
                @click="prev"
              >
                <el-icon><ArrowLeft /></el-icon>
              </span>
              <span>第 {{ index + 1 }} 张</span>
              <span
                class="media-viewer__btn media-viewer__next"
                :class="{ 'last-arrow': !infinite && isLast }"
                @click="next"
                vv-show="infinite || !isLast"
              >
                <el-icon><ArrowRight /></el-icon>
              </span>
            </div>
            <slot name="arrow-suffix" v-bind="{ index }"></slot>
          </div>
          <!-- </template> -->
          <!-- ACTIONS -->
          <div v-if="isImage" class="w-full flex justify-start">
            <div class="media-viewer__btn media-viewer__actions">
              <div class="media-viewer__actions__inner">
                <el-icon @click="handleActions('zoomOut')"><ZoomOut /></el-icon>
                <el-icon @click="handleActions('zoomIn')"><ZoomIn /></el-icon>
                <el-icon @click="toggleMode">
                  <ScaleToOriginal v-if="mode.name === 'contain'" />
                  <FullScreen v-if="mode.name === 'original'" />
                </el-icon>
                <el-icon @click="handleActions('anticlocelise')"><RefreshLeft /></el-icon>
                <el-icon @click="handleActions('clocelise')"><RefreshRight /></el-icon>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </transition>
</template>

<script setup lang="ts">
import { computed, ref, onMounted, watch, nextTick } from 'vue'
import {
  Close,
  ArrowRight,
  ArrowLeft,
  RefreshRight,
  RefreshLeft,
  FullScreen,
  ScaleToOriginal,
  ZoomIn,
  ZoomOut
} from '@element-plus/icons-vue'

type PropsType = {
  urlList: any[]
  zIndex?: number
  initialIndex?: number
  infinite?: boolean
  hideOnClickModal?: boolean
}

// 是否是火狐浏览器
const isFirefox = function () {
  return !!window.navigator.userAgent.match(/firefox/i)
}

const mousewheelEventName = isFirefox() ? 'DOMMouseScroll' : 'mousewheel'
const CLOSE_EVENT = 'close'
const SWITCH_EVENT = 'switch'
const INDEX_CHANGE_EVENT = 'indexChange'

const props = withDefaults(defineProps<PropsType>(), {
  urlList: () => [],
  zIndex: 2000,
  initialIndex: 0,
  infinite: true,
  hideOnClickModal: false
})

const emit = defineEmits([CLOSE_EVENT, SWITCH_EVENT])

const EVENT_CODE = {
  tab: 'Tab',
  enter: 'Enter',
  space: 'Space',
  left: 'ArrowLeft', // 37
  up: 'ArrowUp', // 38
  right: 'ArrowRight', // 39
  down: 'ArrowDown', // 40
  esc: 'Escape',
  delete: 'Delete',
  backspace: 'Backspace'
}

defineOptions({
  name: 'MediaViewer'
})

const rafThrottle = function (fn) {
  let locked = false
  return function (...args) {
    if (locked) return
    locked = true
    window.requestAnimationFrame(() => {
      fn.apply(this, args)
      locked = false
    })
  }
}

const Mode = {
  CONTAIN: {
    name: 'contain',
    icon: 'el-icon-full-screen'
  },
  ORIGINAL: {
    name: 'original',
    icon: 'el-icon-c-scale-to-original'
  }
}

let _keyDownHandler: any = null
let _mouseWheelHandler: any = null
let _dragHandler: any = null

const loading = ref(true)
// const index = ref(props.initialIndex)
const index = defineModel('index', { type: Number, default: 0 })

const wrapper = ref<any>(null)
const media = ref<any>(null)
const mode = ref(Mode.CONTAIN)
const transform = ref({
  scale: 1,
  deg: 0,
  offsetX: 0,
  offsetY: 0,
  enableTransition: false
})

const isSingle = computed(() => {
  const { urlList } = props
  return urlList.length <= 1
})

const isFirst = computed(() => {
  return index.value === 0
})

const isLast = computed(() => {
  return index.value === props.urlList.length - 1
})

const currentMedia = computed(() => {
  return props.urlList[index.value]
})

const isVideo = computed(() => {
  const currentUrl = props.urlList[index.value]
  return currentUrl?.endsWith('.mp4')
})

const isImage = computed(() => {
  const currentUrl = props.urlList[index.value]
  return currentUrl?.endsWith('.jpg') || currentUrl?.endsWith('.png')
})

const mediaStyle = computed(() => {
  const { scale, deg, offsetX, offsetY, enableTransition } = transform.value
  const style: any = {
    transform: `scale(${scale}) rotate(${deg}deg)`,
    transition: enableTransition ? 'transform .3s' : '',
    marginLeft: `${offsetX}px`,
    marginTop: `${offsetY}px`
  }
  if (mode.value.name === Mode.CONTAIN.name) {
    style.maxWidth = style.maxHeight = '100%'
  }
  return style
})

function hide() {
  deviceSupportUninstall()
  emit(CLOSE_EVENT)
}

function deviceSupportInstall() {
  _keyDownHandler = rafThrottle((e) => {
    switch (e.code) {
      // ESC
      case EVENT_CODE.esc:
        hide()
        break
      // SPACE
      case EVENT_CODE.space:
        toggleMode()
        break
      // LEFT_ARROW
      case EVENT_CODE.left:
        prev()
        break
      // UP_ARROW
      case EVENT_CODE.up:
        handleActions('zoomIn')
        break
      // RIGHT_ARROW
      case EVENT_CODE.right:
        next()
        break
      // DOWN_ARROW
      case EVENT_CODE.down:
        handleActions('zoomOut')
        break
    }
  })

  _mouseWheelHandler = rafThrottle((e) => {
    const delta = e.wheelDelta ? e.wheelDelta : -e.detail
    if (delta > 0) {
      handleActions('zoomIn', {
        zoomRate: 0.015,
        enableTransition: false
      })
    } else {
      handleActions('zoomOut', {
        zoomRate: 0.015,
        enableTransition: false
      })
    }
  })

  document.addEventListener('keydown', _keyDownHandler, false)
  document.addEventListener(mousewheelEventName, _mouseWheelHandler, false)
}

function deviceSupportUninstall() {
  document.removeEventListener('keydown', _keyDownHandler, false)
  document.removeEventListener(mousewheelEventName, _mouseWheelHandler, false)
  _keyDownHandler = null
  _mouseWheelHandler = null
}

function handleMediaLoad() {
  loading.value = false
  // console.log('加载成功:', loading.value)
}

function handleMediaError(e) {
  loading.value = false
  // console.log('加载失败', loading.value)
}

function handleMouseDown(e) {
  if (loading.value || e.button !== 0) return

  const { offsetX, offsetY } = transform.value
  const startX = e.pageX
  const startY = e.pageY

  const divLeft = wrapper.value.clientLeft
  const divRight = wrapper.value.clientLeft + wrapper.value.clientWidth
  const divTop = wrapper.value.clientTop
  const divBottom = wrapper.value.clientTop + wrapper.value.clientHeight

  _dragHandler = rafThrottle((ev) => {
    transform.value = {
      ...transform.value,
      offsetX: offsetX + ev.pageX - startX,
      offsetY: offsetY + ev.pageY - startY
    }
  })
  document.addEventListener('mousemove', _dragHandler, false)
  document.addEventListener(
    'mouseup',
    (e) => {
      const mouseX = e.pageX
      const mouseY = e.pageY
      if (mouseX < divLeft || mouseX > divRight || mouseY < divTop || mouseY > divBottom) {
        reset()
      }
      document.removeEventListener('mousemove', _dragHandler, false)
    },
    false
  )

  e.preventDefault()
}

function reset() {
  transform.value = {
    scale: 1,
    deg: 0,
    offsetX: 0,
    offsetY: 0,
    enableTransition: false
  }
}

function toggleMode() {
  if (loading.value) return

  const modeNames = Object.keys(Mode)
  const modeValues = Object.values(Mode)
  const currentMode = mode.value.name
  const index = modeValues.findIndex((i) => i.name === currentMode)
  const nextIndex = (index + 1) % modeNames.length
  mode.value = Mode[modeNames[nextIndex]]
  reset()
}

function prev() {
  if (isFirst.value && !props.infinite) return
  const len = props.urlList.length
  index.value = (index.value - 1 + len) % len
}

function next() {
  if (isLast.value && !props.infinite) return
  const len = props.urlList.length
  index.value = (index.value + 1) % len
}

function handleActions(action, options = {}) {
  // console.log(action, loading.value, 'action')

  if (loading.value) return
  const { zoomRate, rotateDeg, enableTransition } = {
    zoomRate: 0.2,
    rotateDeg: 90,
    enableTransition: true,
    ...options
  }
  switch (action) {
    case 'zoomOut':
      if (transform.value.scale > 0.2) {
        transform.value.scale = parseFloat((transform.value.scale - zoomRate).toFixed(3))
      }
      break
    case 'zoomIn':
      transform.value.scale = parseFloat((transform.value.scale + zoomRate).toFixed(3))
      break
    case 'clocelise':
      transform.value.deg += rotateDeg
      break
    case 'anticlocelise':
      transform.value.deg -= rotateDeg
      break
  }
  transform.value.enableTransition = enableTransition
}

watch(currentMedia, () => {
  nextTick(() => {
    let $media = media.value
    if (isVideo.value) {
      $media?.filtr((it) => it.nodeName === 'VIDEO')
    } else if (isImage.value) {
      $media?.filtr((it) => it.nodeName === 'IMG')
    }
    let complete = $media?.some((it) => it.complete)
    // console.log($media, isImage.value, isVideo.value, '$media')

    if (!complete) {
      loading.value = true
      // console.log('currentMedia：', loading.value)
    }
  })
})

watch(index, (val) => {
  reset()
  emit(SWITCH_EVENT, val)
})

onMounted(() => {
  deviceSupportInstall()
  wrapper.value?.focus?.()
})
</script>
<style scoped lang="scss">
$arrow-size: 32px;

.media-viewer__wrapper {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  .is-disabled {
    pointer-events: none;
    cursor: not-allowed !important;
  }
  .media-viewer__mask {
    position: absolute;
    width: 100%;
    height: 100%;
    top: 0;
    left: 0;
    opacity: 0.5;
    background: #000;
  }
  .media-viewer__close {
    position: absolute;
    width: 44px;
    height: 44px;
    font-size: 24px;
    color: #fff;
    background-color: var(--el-text-color-regular);
    border-color: #fff;
    top: 40px;
    right: 40px;
    font-size: 40px;
  }
  .media-viewer__btn {
    z-index: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    opacity: 0.8;
    cursor: pointer;
    box-sizing: border-box;
    user-select: none;
  }
  .media-viewer__arrow {
    @include flex(flex-start, center);
    // @include size(100%, 44px);
    height: 44px;
    gap: 8px;
    font-size: 20px;
    z-index: 2;
    color: #fff;
    .media-viewer__prev {
      width: $arrow-size;
      height: $arrow-size;
      font-size: 24px;
      color: #fff;
      background-color: var(--el-text-color-regular);
      &.first-arrow {
        cursor: not-allowed;
        color: gray;
        &:hover {
          border: none;
        }
      }
      //   transition: all 0.5s;
      &:hover {
        border: 2px solid #fff;
      }
    }
    .media-viewer__next {
      text-indent: 2px;
      width: $arrow-size;
      height: $arrow-size;
      font-size: 24px;
      color: #fff;
      background-color: var(--el-text-color-regular);
      //   transition: all 0.5s;
      &:hover {
        border: 2px solid #fff;
      }
      &.last-arrow {
        cursor: not-allowed;
        color: gray;
        &:hover {
          border: none;
        }
      }
    }
  }

  .media-viewer_content {
    @apply flex w-full h-full box-border p-4;
    .media-viewer__canvas {
      position: relative;
      //   width: 70%;
      flex: 1;
      height: 100%;
      display: flex;
      justify-content: flex-end;
      align-items: center;
      user-select: none;
    }
    .media-viewer__operation {
      position: static;
      width: 40%;
      height: 100%;
      display: flex;
      justify-content: center;
      align-items: center;
      flex-direction: column;
      user-select: none;
      padding-left: 40px;
      box-sizing: border-box;
    }

    .media-viewer__actions {
      // position: absolute;
      // right: -80px;
      // bottom: 30px;
      // transform: translate(-50%);
      width: 282px;
      height: 44px;
      padding: 0 23px;
      background-color: var(--el-text-color-regular);
      border-color: #fff;
      border-radius: 22px;
      .media-viewer__actions__inner {
        width: 100%;
        height: 100%;
        cursor: default;
        font-size: 23px;
        color: #fff;
        display: flex;
        align-items: center;
        justify-content: space-around;
      }
    }
  }
}
</style>
